package com.lyu.xiaolvshu.user.biz.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.lyu.framework.biz.context.holder.LoginUserContextHolder;
import com.lyu.framework.common.enums.DeletedEnum;
import com.lyu.framework.common.enums.StatusEnum;
import com.lyu.framework.common.exception.BizException;
import com.lyu.framework.common.response.Response;
import com.lyu.framework.common.util.JsonUtils;
import com.lyu.framework.common.util.ParamUtils;
import com.lyu.xiaolvshu.user.biz.constant.RedisKeyConstants;
import com.lyu.xiaolvshu.user.biz.constant.RoleConstants;
import com.lyu.xiaolvshu.user.biz.domain.dos.RoleDO;
import com.lyu.xiaolvshu.user.biz.domain.dos.UserDO;
import com.lyu.xiaolvshu.user.biz.domain.dos.UserRoleDO;
import com.lyu.xiaolvshu.user.biz.domain.mapper.RoleDOMapper;
import com.lyu.xiaolvshu.user.biz.domain.mapper.UserDOMapper;
import com.lyu.xiaolvshu.user.biz.domain.mapper.UserRoleDOMapper;
import com.lyu.xiaolvshu.user.biz.enums.ResponseCodeEnum;
import com.lyu.xiaolvshu.user.biz.enums.SexEnum;
import com.lyu.xiaolvshu.user.biz.model.vo.UpdateUserInfoReqVO;
import com.lyu.xiaolvshu.user.biz.rpc.DistributedIdGeneratorRpcService;
import com.lyu.xiaolvshu.user.biz.rpc.OssRpcService;
import com.lyu.xiaolvshu.user.biz.service.UserService;
import com.lyu.xiaolvshu.user.dto.req.FindUserByIdReqDTO;
import com.lyu.xiaolvshu.user.dto.req.FindUserByPhoneReqDTO;
import com.lyu.xiaolvshu.user.dto.req.RegisterUserReqDTO;
import com.lyu.xiaolvshu.user.dto.req.UpdateUserPasswordReqDTO;
import com.lyu.xiaolvshu.user.dto.resp.FindUserByIdRespDTO;
import com.lyu.xiaolvshu.user.dto.resp.FindUserByPhoneRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    private UserDOMapper userDOMapper;

    @Resource
    private RoleDOMapper roleDOMapper;

    @Resource
    private UserRoleDOMapper userRoleDOMapper;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource
    private OssRpcService ossRpcService;

    @Resource
    private DistributedIdGeneratorRpcService distributedIdGeneratorRpcService;

    private static final Cache<Long, FindUserByIdRespDTO> LOCAL_CACHE = Caffeine.newBuilder()
            .initialCapacity(10000)
            .maximumSize(10000)
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();

    @Override
    public Response<?> updateUserInfo(UpdateUserInfoReqVO updateUserInfoReqVO) {
        UserDO userDO = new UserDO();
        userDO.setId(LoginUserContextHolder.getUserId());

        boolean needUpdate = false;

        MultipartFile avatarFile = updateUserInfoReqVO.getAvatar();

        if (Objects.nonNull(avatarFile)) {
            // 调用对象存储服务上传文件
            String avatar = ossRpcService.uploadFile(avatarFile);
            if (StringUtils.isBlank(avatar)) {
                throw new BizException(ResponseCodeEnum.UPLOAD_AVATAR_FAIL);
            }
            log.info("==> 调用 oss 服务成功，上传头像，url：{}", avatar);
            userDO.setAvatar(avatar);
            needUpdate = true;
        }

        String nickName = updateUserInfoReqVO.getNickname();
        if (StringUtils.isNotBlank(nickName)) {
            Preconditions.checkArgument(ParamUtils.checkNickname(nickName), ResponseCodeEnum.NICK_NAME_VALID_FAIL.getErrorMessage());
            userDO.setNickname(nickName);
            needUpdate = true;
        }

        String xiaolvshuId = updateUserInfoReqVO.getXiaolvshuId();
        if (StringUtils.isNotBlank(xiaolvshuId)) {
            Preconditions.checkArgument(ParamUtils.checkXiaolvshuId(xiaolvshuId), ResponseCodeEnum.XIAOHASHU_ID_VALID_FAIL.getErrorMessage());
            userDO.setXiaolvshuId(xiaolvshuId);
            needUpdate = true;
        }

        // 性别
        Integer sex = updateUserInfoReqVO.getSex();
        if (Objects.nonNull(sex)) {
            Preconditions.checkArgument(SexEnum.isValid(sex), ResponseCodeEnum.SEX_VALID_FAIL.getErrorMessage());
            userDO.setSex(sex);
            needUpdate = true;
        }

        // 生日
        LocalDate birthday = updateUserInfoReqVO.getBirthday();
        if (Objects.nonNull(birthday)) {
            userDO.setBirthday(birthday);
            needUpdate = true;
        }

        // 个人简介
        String introduction = updateUserInfoReqVO.getIntroduction();
        if (StringUtils.isNotBlank(introduction)) {
            Preconditions.checkArgument(ParamUtils.checkLength(introduction, 100), ResponseCodeEnum.INTRODUCTION_VALID_FAIL.getErrorMessage());
            userDO.setIntroduction(introduction);
            needUpdate = true;
        }

        // 背景图
        MultipartFile backgroundImgFile = updateUserInfoReqVO.getBackgroundImg();
        if (Objects.nonNull(backgroundImgFile)) {
            // 调用对象存储服务上传文件
            String backgroundImg = ossRpcService.uploadFile(backgroundImgFile);
            if (StringUtils.isBlank(backgroundImg)) {
                throw new BizException(ResponseCodeEnum.UPLOAD_BACKGROUND_IMG_FAIL);
            }
            log.info("==> 调用 oss 服务成功，上传背景图，url：{}", backgroundImg);
            userDO.setBackgroundImg(backgroundImg);
            needUpdate = true;
        }

        if (needUpdate) {
            userDO.setUpdateTime(LocalDateTime.now());
            userDOMapper.updateByPrimaryKeySelective(userDO);
        }

        return Response.success();
    }

    @Override
    public Response<Long> register(RegisterUserReqDTO registerUserReqDTO) {
        String phone = registerUserReqDTO.getPhone();

        UserDO userDO = userDOMapper.selectByPhone(phone);

        if (Objects.nonNull(userDO)) {
            return Response.success(userDO.getId());
        }

        Long userId = transactionTemplate.execute(status -> {
            try {
                String xiaolvshuId = distributedIdGeneratorRpcService.getXiaolvshuId();
                String generatorUserId = distributedIdGeneratorRpcService.getUserId();
                UserDO userDONew = UserDO.builder()
                        .id(Long.valueOf(generatorUserId))
                        .phone(phone)
                        .xiaolvshuId(xiaolvshuId)
                        .nickname("小绿树" + xiaolvshuId)
                        .status(StatusEnum.ENABLE.getValue())
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .isDeleted(DeletedEnum.NO.getValue())
                        .build();

                userDOMapper.insert(userDONew);

                Long userIdNew = userDONew.getId();

                UserRoleDO userRoleDO = UserRoleDO.builder()
                        .userId(userIdNew)
                        .roleId(RoleConstants.COMMON_USER_ROLE_ID)
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .isDeleted(DeletedEnum.NO.getValue())
                        .build();

                userRoleDOMapper.insert(userRoleDO);

                RoleDO roleDO = roleDOMapper.selectByPrimaryKey(RoleConstants.COMMON_USER_ROLE_ID);

                List<String> roles = Lists.newArrayList();
                roles.add(roleDO.getRoleKey());
                String userROleKey = RedisKeyConstants.buildUserRoleKey(userIdNew);
                redisTemplate.opsForValue().set(userROleKey, JsonUtils.toJsonString(roles));

                return userIdNew;
            } catch (Exception e) {
                status.setRollbackOnly();
                log.error("==> 系统注册用户异常: ", e);
                throw new BizException(ResponseCodeEnum.SYSTEM_ERROR);
            }
        });
        return Response.success(userId);
    }

    @Override
    public Response<FindUserByPhoneRespDTO> findUserByPhone(FindUserByPhoneReqDTO findUserByPhoneReqDTO) {
        String phone = findUserByPhoneReqDTO.getPhone();

        UserDO userDO = userDOMapper.selectByPhone(phone);

        if (Objects.isNull(userDO)) {
            throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
        }

        FindUserByPhoneRespDTO findUserByPhoneRespDTO = FindUserByPhoneRespDTO.builder()
                .id(userDO.getId())
                .password(userDO.getPassword())
                .build();
        return Response.success(findUserByPhoneRespDTO);
    }

    @Override
    public Response<?> updateUserPassword(UpdateUserPasswordReqDTO updateUserPasswordReqDTO) {
        Long userId = LoginUserContextHolder.getUserId();

        UserDO userDO = UserDO.builder()
                .id(userId)
                .password(updateUserPasswordReqDTO.getEncodePassword())
                .updateTime(LocalDateTime.now())
                .build();

        userDOMapper.updateByPrimaryKeySelective(userDO);

        return Response.success();
    }

    @Override
    public Response<FindUserByIdRespDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO) {
        Long userId = findUserByIdReqDTO.getId();

        FindUserByIdRespDTO userInfoLocalCacheValue = LOCAL_CACHE.getIfPresent(userId);

        if(Objects.nonNull(userInfoLocalCacheValue)) {
            log.info("==> 命中了本地缓存；{}", userInfoLocalCacheValue);
            return Response.success(userInfoLocalCacheValue);
        }

        String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);

        String userInfoRedisValue = (String) redisTemplate.opsForValue().get(userInfoRedisKey);

        if (StringUtils.isNotBlank(userInfoRedisValue)) {
            FindUserByIdRespDTO findUserByIdRespDTO = JsonUtils.parseObject(userInfoRedisValue, FindUserByIdRespDTO.class);
            threadPoolTaskExecutor.submit(()->{
                if (Objects.nonNull(findUserByIdRespDTO)) {
                    LOCAL_CACHE.put(userId, findUserByIdRespDTO);
                }
            });
            return Response.success(findUserByIdRespDTO);
        }

        UserDO userDO = userDOMapper.selectByPrimaryKey(userId);
        if (Objects.isNull(userDO)) {
            threadPoolTaskExecutor.submit(()->{
                // 过期时间 60 秒
                long expireSeconds = 60;
                redisTemplate.opsForValue().set(userInfoRedisKey, "null", expireSeconds, TimeUnit.SECONDS);
            });
            throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
        }
        FindUserByIdRespDTO findUserByIdRespDTO = FindUserByIdRespDTO.builder()
                .id(userId)
                .nickName(userDO.getNickname())
                .avatar(userDO.getAvatar())
                .build();

        threadPoolTaskExecutor.submit(()->{
            // 过期时间（保底1天 + 随机秒数，将缓存过期时间打散，防止同一时间大量缓存失效，导致数据库压力太大）
            long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
            redisTemplate.opsForValue().set(userInfoRedisKey, JsonUtils.toJsonString(findUserByIdRespDTO), expireSeconds, TimeUnit.SECONDS);
        });

        return Response.success(findUserByIdRespDTO);
    }
}
